/*
 * @Descripttion:
 * @LastEditors: chen.long
 * @LastEditTime: 2024-02-24 15:05:57
 */
import { error, warn } from './logger';
import { YBMICRO_TIPS_NO_URL, YBMICRO_TIPS_STOP_APP } from '../constant';
import { cacheOptions } from '../types/index.type';
import { anchorElementGenerator, getAnchorElementQueryMap } from './element';

/**
 * @Author: chen.long
 * @Descripttion: 停止主应用运行
 * @Param:
 */
export function stopMainAppRun() {
  warn(YBMICRO_TIPS_STOP_APP);
  throw new Error(YBMICRO_TIPS_STOP_APP);
}

/**
 * @Author: chen.long
 * @Descripttion: 合并缓存的参数值和传入的参数值
 * @Param:
 */
export function mergeOptions(
  options: cacheOptions,
  cacheOptions: cacheOptions
) {
  return {
    name: options.name,
    el: options.el || cacheOptions?.el,
    url: options.url || cacheOptions?.url,
    props: options.props || cacheOptions?.props,
    loading: options.loading || cacheOptions?.loading,
    lifecycles: {
      beforeLoad: options.beforeLoad || cacheOptions?.beforeLoad,
      beforeMount: options.beforeMount || cacheOptions?.beforeMount,
      afterMount: options.afterMount || cacheOptions?.afterMount,
      beforeUnmount: options.beforeUnmount || cacheOptions?.beforeUnmount,
      afterUnmount: options.afterUnmount || cacheOptions?.afterUnmount,
      activated: options.activated || cacheOptions?.activated,
      deactivated: options.deactivated || cacheOptions?.deactivated,
      loadError: options.loadError || cacheOptions?.loadError
    }
  };
}

/**
 * @Author: chen.long
 * @Descripttion: 获取绝对路径
 */
export function getAbsolutePath(
  url: string,
  base: string,
  hash?: boolean
): string {
  try {
    // 为空值无需处理
    if (url) {
      // 需要处理hash的场景
      if (hash && url.startsWith('#')) return url;
      return new URL(url, base).href;
    } else return url;
  } catch {
    return url;
  }
}

/**
 * @Author: chen.long
 * @Descripttion: 判断指定参数是否是函数类型
 * @Param:
 * @param {any} value 待判断对象
 */
export function isFunction(value: any): boolean {
  return typeof value === 'function';
}

/**
 * @Author: chen.long
 * @Descripttion: 函数柯里化
 * @Param:
 */
export function compose(
  fnList: Array<Function>
): (...args: Array<string>) => string {
  return function (code: string, ...args: Array<any>) {
    return fnList.reduce(
      (newCode, fn) => (isFunction(fn) ? fn(newCode, ...args) : newCode),
      code || ''
    );
  };
}

/**
 * @Author: chen.long
 * @Descripttion: 匹配内联的样式和脚本
 * @Param:
 * @param {*} match 待匹配的内容
 */
export function getInlineCode(match) {
  const start = match.indexOf('>') + 1;
  const end = match.lastIndexOf('<');
  return match.substring(start, end);
}

/**
 * @Author: chen.long
 * @Descripttion: requestIdleCallback兼容性处理
 */
export const requestIdleCallback =
  window.requestIdleCallback || ((cb: Function) => setTimeout(cb, 1));

export function isHijackingTag(tagName?: string) {
  return (
    tagName?.toUpperCase() === 'LINK' ||
    tagName?.toUpperCase() === 'STYLE' ||
    tagName?.toUpperCase() === 'SCRIPT' ||
    tagName?.toUpperCase() === 'IFRAME'
  );
}

/**
 * @Author: chen.long
 * @Descripttion: 获取当前地址
 */
export function getCurUrl(proxyLocation: Object): string {
  const location = proxyLocation as Location;
  return location.protocol + '//' + location.host + location.pathname;
}

/**
 * @Author: chen.long
 * @Descripttion: 解析url
 * @Param:
 * @param {string} url url字符串
 */
export function appRouteParse(url: string): {
  urlElement: HTMLAnchorElement;
  appHostPath: string;
  appRoutePath: string;
} {
  if (!url) {
    error(YBMICRO_TIPS_NO_URL);
    throw new Error();
  }
  const urlElement = anchorElementGenerator(url);
  const appHostPath = urlElement.protocol + '//' + urlElement.host;
  let appRoutePath = urlElement.pathname + urlElement.search + urlElement.hash;
  if (!appRoutePath.startsWith('/')) appRoutePath = '/' + appRoutePath; // hack ie
  return { urlElement, appHostPath, appRoutePath };
}

/**
 * @Author: chen.long
 * @Descripttion: 当前url的查询参数中是否有给定的id
 * @Param:
 * @param {string} id
 */
export function isMatchSyncQueryById(id: string): boolean {
  const queryMap = getAnchorElementQueryMap(
    anchorElementGenerator(window.location.href)
  );
  return Object.keys(queryMap).includes(id);
}

const fnRegexCheckCacheMap = new WeakMap<any | FunctionConstructor, boolean>();
/**
 * @Author: chen.long
 * @Descripttion: 判断指定函数是否是构造函数
 * @Param:
 * @param {function} fn 待判断的函数
 */
export function isConstructable(fn: () => any | FunctionConstructor) {
  const hasPrototypeMethods =
    fn.prototype &&
    fn.prototype.constructor === fn &&
    Object.getOwnPropertyNames(fn.prototype).length > 1;

  if (hasPrototypeMethods) return true;

  if (fnRegexCheckCacheMap.has(fn)) {
    return fnRegexCheckCacheMap.get(fn);
  }

  let constructable = hasPrototypeMethods;
  if (!constructable) {
    const fnString = fn.toString();
    const constructableFunctionRegex = /^function\b\s[A-Z].*/;
    const classRegex = /^class\b/;
    constructable =
      constructableFunctionRegex.test(fnString) || classRegex.test(fnString);
  }

  fnRegexCheckCacheMap.set(fn, constructable);
  return constructable;
}

/**
 * @Author: chen.long
 * @Descripttion: 劫持元素原型对相对地址的赋值转绝对地址
 */
export function fixElementCtrSrcOrHref(
  iframeWindow: Window,
  elementCtr:
    | typeof HTMLImageElement
    | typeof HTMLAnchorElement
    | typeof HTMLSourceElement
    | typeof HTMLLinkElement
    | typeof HTMLScriptElement
    | typeof HTMLMediaElement,
  attr
): void {
  const rawElementSetAttribute = iframeWindow.Element.prototype.setAttribute;
  elementCtr.prototype.setAttribute = function (
    name: string,
    value: string
  ): void {
    let targetValue = value;
    if (name === attr)
      targetValue = getAbsolutePath(value, this.baseURI || '', true);
    rawElementSetAttribute.call(this, name, targetValue);
  };
  const rawAnchorElementHrefDescriptor = Object.getOwnPropertyDescriptor(
    elementCtr.prototype,
    attr
  );
  const { enumerable, configurable, get, set } = rawAnchorElementHrefDescriptor;
  Object.defineProperty(elementCtr.prototype, attr, {
    enumerable,
    configurable,
    get: function () {
      return get.call(this);
    },
    set: function (href) {
      set.call(this, getAbsolutePath(href, this.baseURI, true));
    }
  });
}

const naughtySafari =
  typeof document.all === 'function' && typeof document.all === 'undefined';
const callableFnCacheMap = new WeakMap<CallableFunction, boolean>();
/**
 * @Author: chen.long
 * @Descripttion: 判断传入的参数是否可以执行
 * 处理safari的情况
 * safari下 typeof document.all === 'undefined' // true  typeof document.all === 'function' // true
 * @Param:
 * @param {any} fn
 */
export const isCallable = (fn: any) => {
  if (callableFnCacheMap.has(fn)) {
    return true;
  }

  const callable = naughtySafari
    ? typeof fn === 'function' && typeof fn !== 'undefined'
    : typeof fn === 'function';
  if (callable) {
    callableFnCacheMap.set(fn, callable);
  }
  return callable;
};

/**
 * @Author: chen.long
 * @Descripttion: 事件触发器
 */
export function eventTrigger(
  el: HTMLElement | Window | Document,
  eventName: string,
  detail?: any
) {
  let event;
  if (typeof window.CustomEvent === 'function') {
    event = new CustomEvent(eventName, { detail });
  } else {
    event = document.createEvent('CustomEvent');
    event.initCustomEvent(eventName, true, false, detail);
  }
  el.dispatchEvent(event);
}

/**
 * @Author: chen.long
 * @Descripttion: 默认资源路径
 */
export function defaultGetPublicPath(entry) {
  if (typeof entry === 'object') {
    return '/';
  }
  try {
    const { origin, pathname } = new URL(entry, location.href);
    const paths = pathname.split('/');
    // 移除最后一个元素
    paths.pop();
    return `${origin}${paths.join('/')}/`;
  } catch (e) {
    console.warn(e);
    return '';
  }
}
